package tiger.oeildetigre;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.util.ArrayList;

import static org.opencv.core.CvType.CV_8UC3;

public class MainActivity extends Activity implements CvCameraViewListener2 {
    private static final String TAG = "OCVSample::Activity";

    private static final Scalar THRESHOLD_RED_LOWER = new Scalar(100, 46, 190);
    private static final Scalar THRESHOLD_RED_UPPER = new Scalar(198, 246, 255);

    private static final Scalar THRESHOLD_BLUE_LOWER = new Scalar(0, 0, 200);
    private static final Scalar THRESHOLD_BLUE_UPPER = new Scalar(100, 255, 255);

    private static final Scalar COLOR_RED = new Scalar(255, 0, 0);
    private static final Scalar COLOR_BLUE = new Scalar(0, 0, 255);
    private static final Scalar COLOR_YELLOW = new Scalar(255, 255, 0);
    private static final Scalar COLOR_GREEN = new Scalar(0, 255, 0);

    private static final double OPTIMAL_MIN_AREA = 290;
    private static final double OPTIMAL_MAX_AREA = 850;
    // TODO: Find optimal values
    private static final double OPTIMAL_MAX_WIDTH = 0;
    private static final double OPTIMAL_MIN_WIDTH = 0;
    private static final double OPTIMAL_MAX_RATIO = 1.7;
    private static final double OPTIMAL_MIN_RATIO = 1.0;
    private static final int SCREEN_HEIGHT = 720;   // rows
    private static final int SCREEN_WIDTH = 1280;   // cols
    private static final double OPTIMAL_MAX_DIST = 1000;    // TODO: Find max distance between red and blue on the beacon
    private static final double OPTIMAL_MAX_HEIGHT = 40.0;
    private static final double OPTIMAL_MIN_HEIGHT = 7.0;

    private boolean isRedRight = false;

    private ArrayList<MatOfPoint> redContours = new ArrayList<>();
    private ArrayList<MatOfPoint> blueContours = new ArrayList<>();

    private CameraBridgeViewBase mOpenCvCameraView;

    private Mat x = new Mat();
    private Mat redCanvas = new Mat();
    private Mat blueCanvas = new Mat();
    private Mat coloredCanvas = new Mat();

    private Mat preview = new Mat(SCREEN_HEIGHT, SCREEN_WIDTH, CV_8UC3, COLOR_BLUE);

    private Mat redHierarchy = new Mat();
    private Mat blueHierarchy = new Mat();

    private Point offset = new Point();


    static {
        System.loadLibrary("opencv_java3");
    }

    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch(status) {
                case LoaderCallbackInterface.SUCCESS: {
                    Log.i(TAG, "OpenCV loaded successfully");
                    mOpenCvCameraView.enableView();
                } break;
                default: {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };

    public MainActivity() {
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        setContentView(R.layout.tutorial1_surface_view);

        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_java_surface_view);

        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);

        mOpenCvCameraView.setCvCameraViewListener(this);

        //mOpenCvCameraView.isHardwareAccelerated();

        Log.i(TAG, "HDA HELLO");
        Log.i(TAG, "HDA " + mOpenCvCameraView.isHardwareAccelerated());

        //CameraMode.setCameraMode();

    }

    @Override
    public void onPause() {
        super.onPause();
        if(mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onResume() {
        super.onResume();
        if(!OpenCVLoader.initDebug()){
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    public void onDestroy() {
        super.onDestroy();
        if(mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    public void onCameraViewStarted(int width, int height) {
    }

    public void onCameraViewStopped() {
    }

    public synchronized Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        double origWidth;
        double origHeight;

        final double RESIZE_FACTOR = 4.0;
        double newWidth;
        double newHeight;



        x.empty();
        redCanvas.empty();
        blueCanvas.empty();
        redHierarchy.empty();
        blueHierarchy.empty();
        coloredCanvas.empty();
        preview.empty();

        redContours.clear();
        blueContours.clear();


        x = inputFrame.rgba();
        origWidth = x.size().width;
        origHeight = x.size().height;

        //Log.i(TAG, "origWidth: " + origWidth);
        //Log.i(TAG, "origHeight: " + origHeight);

        newWidth = origWidth / RESIZE_FACTOR;
        newHeight = origHeight / RESIZE_FACTOR;

        Imgproc.resize(x, coloredCanvas, new Size(newWidth, newHeight), 0.0, 0.0, Imgproc.INTER_CUBIC);

        Imgproc.cvtColor(coloredCanvas, coloredCanvas, Imgproc.COLOR_BGR2HSV_FULL);

        Imgproc.GaussianBlur(coloredCanvas, coloredCanvas, new Size(5, 5), 5);

        coloredCanvas.copyTo(redCanvas);
        coloredCanvas.copyTo(blueCanvas);

//        Imgproc.GaussianBlur(redCanvas, redCanvas, new Size(5, 5), 2);
//        Imgproc.GaussianBlur(blueCanvas, blueCanvas, new Size(5, 5), 2);

        // Finding red
        Core.inRange(
                redCanvas,
                THRESHOLD_RED_LOWER,
                THRESHOLD_RED_UPPER,
                redCanvas);

        // Finding blue
        Core.inRange(
                blueCanvas,
                THRESHOLD_BLUE_LOWER,
                THRESHOLD_BLUE_UPPER,
                blueCanvas);

        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));

        Imgproc.erode(redCanvas, redCanvas, element);
        //Imgproc.erode(redCanvas, redCanvas, element);
        Imgproc.dilate(redCanvas, redCanvas, element);
        Imgproc.dilate(redCanvas, redCanvas, element);
        Imgproc.dilate(redCanvas, redCanvas, element);

        Imgproc.erode(blueCanvas, blueCanvas, element);
        Imgproc.erode(blueCanvas, blueCanvas, element);
        Imgproc.dilate(blueCanvas, blueCanvas, element);
        Imgproc.dilate(blueCanvas, blueCanvas, element);
        Imgproc.dilate(blueCanvas, blueCanvas, element);


        // Find contour in red mat
        Imgproc.findContours(redCanvas, redContours, redHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // Find contour in blue mat
        Imgproc.findContours(blueCanvas, blueContours, blueHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // Draw contour in coloredCanvas
        // red
        Imgproc.drawContours(coloredCanvas, redContours, -2,
                COLOR_YELLOW, 1, 8, redHierarchy, Imgproc.INTER_MAX, offset);
        // blue
        Imgproc.drawContours(coloredCanvas, blueContours, -2,
                COLOR_GREEN, 1, 8, blueHierarchy, Imgproc.INTER_MAX, offset);

        int redSize = redContours.size();;
        int blueSize = blueContours.size();
        int targetRedIndex = -1;
        int targetBlueIndex = -1;
        double targetRedArea = 0;
        double targetBlueArea = 0;

        Log.i(TAG, "SB Size of Red Contour"+ redSize);
        Log.i(TAG, "SB Size of Blue Contour"+ blueSize);

        if(redSize != 0 && blueSize != 0) {
            for(int i = 0; i < redSize; i++) {
                MatOfPoint contour = redContours.get(i);
                if(contourValidation(contour)) {
                    targetRedIndex = i;
                    targetRedArea = Imgproc.contourArea(contour);
                }
            }

            for(int i = 0; i < blueSize; i++) {
                MatOfPoint contour = blueContours.get(i);
                if(contourValidation(contour)) {
                    targetBlueIndex = i;
                    targetBlueArea = Imgproc.contourArea(contour);
                }
            }

            if(targetRedIndex != -1 && targetBlueIndex != -1) {
                Log.i(TAG, "SB Area of Red Contour  "+ targetRedArea);
                Log.i(TAG, "SB Area of Blue Contour "+ targetBlueArea);

                Rect redRect = Imgproc.boundingRect(redContours.get(targetRedIndex));
                Rect blueRect = Imgproc.boundingRect(blueContours.get(targetBlueIndex));

                Log.i(TAG, "SB DMN  Red Wid: "+ redRect.width + " Hei: " + redRect.height);
                Log.i(TAG, "SB DMN  Blue Wid: "+ blueRect.width + " Hei: " + blueRect.height);
                Log.i(TAG, "SB DMN  Red Ratio: "+ (double)redRect.width / (double) redRect.height);


                // Draw rectangles
                // Red
                Imgproc.rectangle(coloredCanvas, redRect.tl(), redRect.br(),
                        COLOR_RED, 2);
                Imgproc.rectangle(coloredCanvas, blueRect.tl(), blueRect.br(),
                        COLOR_BLUE, 2);

                // Calc red-blue distance
                double horiDis = 0;
                Point redCenter = new Point((redRect.tl().x + redRect.width/2), (redRect.tl().y + redRect.height/2));
                Point blueCenter = new Point((blueRect.tl().x + blueRect.width/2), (blueRect.tl().y + blueRect.height/2));

                horiDis = redCenter.y - blueCenter.y;

                if(horiDis <= OPTIMAL_MAX_DIST) {
                    if(horiDis > 0) {
                        isRedRight = true;  // Red is on the right
                        Log.i(TAG, "POS Blue - Red");
                    }
                    else {
                        isRedRight = false; // Red is on the left
                        Log.i(TAG, "POS Red - Blue");
                    }
                }

            }

        }


        // Resize to full screen coloredCanvas
        //Imgproc.resize(coloredCanvas, coloredCanvas, new Size(origWidth, origHeight), 0.0, 0.0, Imgproc.INTER_CUBIC);

        Imgproc.resize(coloredCanvas, coloredCanvas, new Size(origWidth/2, origHeight/2), 0.0, 0.0, Imgproc.INTER_CUBIC);
//        Imgproc.resize(x, x, new Size(origWidth/2, origHeight/2), 0.0, 0.0, Imgproc.INTER_CUBIC);

        int width = coloredCanvas.width();
        int height = coloredCanvas.height();

        Imgproc.circle(preview, new Point(700, 360),
                2, COLOR_GREEN, 15, Imgproc.LINE_8, 0);

       // double centerHsv[] = coloredCanvas.get(width/2, height/2);
       // Log.i(TAG, "Center HSV: " + centerHsv[0] + " " + centerHsv[1] + " " + centerHsv[2]);

        // Copy coloredCanvas into preview as a submat
        coloredCanvas.copyTo(preview.rowRange(0, height).colRange(0, width));
//        x.copyTo(preview.rowRange(100, 100+height).colRange(200, 200+width));

        System.gc();

        return preview;
    }

//    private static Mat closeRangeTest(CvCameraViewFrame inputFrame) {
//        Mat preview = inputFrame.rgba();
//
//
//        return preview;
//    }


    private static boolean contourValidation(MatOfPoint contour) {
        if(testArea(contour) && testRatio(contour) && testWidth(contour) && testHeight(contour)) {
            return true;
        }
        else {
            return false;
        }
    }

    private static boolean testArea(MatOfPoint contour) {
        double area = Imgproc.contourArea(contour);
        Log.i("ATe", " " + area);
//        if(area >= OPTIMAL_MIN_AREA && area <= OPTIMAL_MAX_AREA) {
//            return true;
//        }
//        else {
//            return false;
//        }
        return true;
    }

    private static boolean testWidth(MatOfPoint contour) {
        // TODO
        double width = contour.width();
        Log.i("CH", "Wid " + width);

//        if(width <= OPTIMAL_MAX_WIDTH && width >= OPTIMAL_MIN_WIDTH) {
//            return true;
//        }
//        else {
//            return false;
//        }
        return true;
    }

    private static boolean testHeight(MatOfPoint contour) {
        double height = contour.height();
        Log.i("CH", "Hei " + height);
        if(height <= OPTIMAL_MAX_HEIGHT && height >= OPTIMAL_MIN_HEIGHT) {
            return true;
        }
        else {
            return false;
        }
    }

    private static boolean testRatio(MatOfPoint contour) {
        // TODO
//        double width = contour.width();
//        double height = contour.height();
//        double ratio = width / height;
//
//        Log.i(TAG, "ratioWH " + ratio);
//
//        if(ratio <= OPTIMAL_MAX_RATIO && ratio >= OPTIMAL_MIN_RATIO) {
//            return true;
//        }
//        else {
//            return false;
//        }
        return true;
    }

}
